﻿/**
 * Copyright (c) 2012, IceRealm.org - all rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Project Page: https://mcts.codeplex.com
 * Contact:      http://www.icerealm.org/contact
 *               icedragon@quickfox.org
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace Minecraft_Text_Splitter
{
    class BlockSplitter
    {
        /*** CONSTANTS *******************************************************/
        public const int MIN_BLOCK_SIZE = 10; // bytes


        /*** PROPERTIES ******************************************************/
        public int BlockSize
        {
            get { return blockSize; }
            set { ChangeBlockSize(value); }
        }

        public int CurrentStartOffset { get { return blockStartOffsets[CurrentBlock]; } }
        public int CurrentEndOffset { get { return blockEndOffsets[CurrentBlock]; } }
        public int CurrentBlock { get; private set; }
        public int TotalBlocks { get { return blockStartOffsets.Count; } }
        public bool HasNext { get { return CurrentBlock < TotalBlocks - 1; } }
        public bool HasPrev { get { return CurrentBlock > 0; } }
        public string NextBlock { get { return HasNext ? GetBlock(CurrentBlock+2, true) : GetBlock(TotalBlocks, true); } }
        public string PrevBlock { get { return HasPrev ? GetBlock(CurrentBlock, true)   : GetBlock(1, true); } }
        public bool Fresh = true;


        /*** CONSTRUCTORS ****************************************************/
        public BlockSplitter()
        {
            CurrentBlock = 0;
        }

        public BlockSplitter(int newBlockSize) : this()
        {
            blockSize = newBlockSize < MIN_BLOCK_SIZE ? MIN_BLOCK_SIZE : newBlockSize;
        }


        /*** METHODS *********************************************************/
        public void ProcessText(String text, bool resetCurrentBlock = false)
        {
            StringBuilder cur_block = new StringBuilder("");

            // Offsets & Indexes
            int i;
            int o_block_start;
            int o_block_end;

            buffer = text;
            clearBlocks();

            i = o_block_start = findFirstChar();
            if (i < 0)
                throw new Exception("No text");

            while (i < buffer.Length)
            {
                o_block_end = findEndOfWordRight(o_block_start + BlockSize);
                
                // Assertion
                if (o_block_start == o_block_end && o_block_start+BlockSize >= buffer.Length)
                    throw new Exception("The thing I thought wouldn't go wrong did - let me know (icedragon@quickfox.org)");

                // Assume we have a huge-ass word we have to split further into parts
                if (o_block_start >= o_block_end)
                    o_block_end = o_block_start + BlockSize;

                addBlock(o_block_start, o_block_end);
                i = o_block_start = findFirstChar(o_block_end);
                if (i < 0)
                    break;
            }

            Fresh = true;
        }

        public string GetBlock(int n, bool resetCursor = false)
        {
            int i = n-1;

            if (TotalBlocks == 0)
                return "";
            else if (Fresh)
            {
                Fresh = false;
                return GetBlock(1, true); // If it just loaded, either button will load the first entry first.
            }
            else if (n > TotalBlocks)
                return GetBlock(TotalBlocks, resetCursor);
            else if (n < 1)
                return GetBlock(1, resetCursor);

            if (resetCursor)
                CurrentBlock = i;

            return buffer.Substring(blockStartOffsets[i], blockEndOffsets[i] - blockStartOffsets[i]);
        }

        public void ChangeBlockSize(int newSize, bool reloadBuffer = true)
        {
            blockSize = newSize < MIN_BLOCK_SIZE ? MIN_BLOCK_SIZE : newSize;
            if (reloadBuffer)
                ProcessText(buffer);
        }


        /*** PRIVATES (no touching a class' privates!) ***********************/
        private int blockSize;
        private String buffer = null;
        private List<int> blockStartOffsets = new List<int>();
        private List<int> blockEndOffsets = new List<int>();

        private void clearBlocks(bool resetCurrentBlock = false)
        {
            blockStartOffsets.Clear();
            blockEndOffsets.Clear();
            if (resetCurrentBlock)
                CurrentBlock = 0;
        }

        private void addBlock(int start, int end)
        {
            blockStartOffsets.Add(start);
            blockEndOffsets.Add(end);
        }

        private int findFirstChar(int index = 0)
        {
            while (index < buffer.Length)
            {
                if (buffer[index] > ' ')
                    return index;
                index++;
            }
            return -1;
        }

        private int findEndOfWord(int index = 0)
        {
            while (index < buffer.Length && buffer[index] != ' ') index++;
            return index;
        }

        private int findEndOfWordRight(int index = 0)
        {
            if (index >= buffer.Length)
                return buffer.Length;

            if (index + 1 < buffer.Length || buffer[index + 1] > 0x20)
            {
                while (index > 0 && buffer[index] != ' ') index--;
                while (index > 0 && buffer[index] == ' ') index--;
            }
            return index+1;
        }
    }
}
